package ai4games_tests.soccer_p;

import game2dai.entities.*;
import game2dai.entityshapes.ps.*;
import game2dai.maths.*;
import game2dai.*;
import game2dai.entityshapes.*;
import game2dai.fsm.*;
import game2dai.steering.*;
import game2dai.utils.*;
import game2dai.graph.*;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;

public class Game extends PApplet {

	World world;
	Pitch pitch;
	PFont font;
	// Offset of pitch within window
	int offsetX = 20, offsetY = 60;
	// State info colours
	int controlCol, threatCol, receiverCol, supportCol;
	float controlDiam = 25, typeDiam = 29, threatDiam = 33;

	boolean showTypes, showStates;

	StopWatch sw = new StopWatch();

	public void setup(){
		size(640, 450);
		controlCol = color(0,0,255);
		threatCol = color(255,0,0);
		receiverCol = color(0,255,0);
		supportCol = color(255,255,0);

		font = createFont("Sans Serif", 14);
		world = new World(600,300,100,100);	
		world.noOverlap(true);
		pitch = new Pitch(this, world, 600, 300);	
		pitch.FSM().setCurrentState(PitchPlayersOff.state());
		world.add(pitch);
		pitch.Z(-10);
		sw.reset();
	}

	public void keyReleased(){
		if(key == '1')
			showStates = !showStates;
		if(key == '2')
			showTypes = !showTypes;
		if(key == '3')
			pitch.showSpots = !pitch.showSpots;
		if(key == 'n' || key == 'N'){
			pitch.sw.reset();
			pitch.FSM().changeState(PitchPlayersOff.state());
		}
	}

	public void drawStateInfo(){
		int side = 0;
		float tw;
		String teamState;

		fill(0);
		teamState = pitch.FSM().getCurrentState().name;
		tw = textWidth(teamState);
		text(teamState, (pitch.length - tw)/2, - 8);
		for(Team t : pitch.teams){
			teamState = t.FSM().getCurrentState().name;
			tw = (t.pitchSide == 0) ? 110 : pitch.length - 110 - textWidth(teamState);
			text(teamState, tw, - 8);
			for(PlayerBase p : t.players){
				Vector2D pos = p.pos();
				if(pos.y <= pitch.width){
					teamState = p.name() + " " + p.FSM().getCurrentState().name;
					text(teamState, (float)pos.x, (float)pos.y - 10);
				}
			}
			side++;
		}
	}

	public void drawTeamInfo(){
		noFill();
		strokeWeight(2);
		ellipseMode(CENTER);
		Team[] teams = pitch.teams;
		PlayerBase player;
		strokeWeight(2);
		for(Team t : teams){
			player = t.getControllingPlayer();
			if(player != null){
				stroke(controlCol);
				ellipse((float)player.pos().x, (float) player.pos().y, controlDiam, controlDiam);
				if(player.isThreatened()){
					stroke(threatCol);
					ellipse((float)player.pos().x, (float) player.pos().y, threatDiam, threatDiam);
				}
			}
			player = t.getSupportingPlayer();
			if(player != null){
				stroke(supportCol);
				ellipse((float)player.pos().x, (float) player.pos().y, typeDiam, typeDiam);
			}
			player = t.getReceivingPlayer();
			if(player != null){
				stroke(receiverCol);
				ellipse((float)player.pos().x, (float) player.pos().y, typeDiam, typeDiam);
			}
		}
	}

	public void drawLegend(){
		noStroke();
		fill(30,128,30);
		rect(0, pitch.width + 8, pitch.length, 72);
		fill(0);
		textFont(font);
		text("[1] toggle state names", 10, pitch.width + 24);
		text("[2] toggle player highlights", 10, pitch.width + 40);
		text("[3] toggle supporting spots", 10, pitch.width + 56);
		text("[N] start new match", 10, pitch.width + 72);

		text("Player Highlights", 410, pitch.width + 24);
		text("Controlling", 340, pitch.width + 40);
		text("When threatened", 340, pitch.width + 56);
		text("Supporting", 500, pitch.width + 40);
		text("Receivng", 500, pitch.width + 56);
		strokeWeight(2);
		noFill();
		int r = 8;
		stroke(controlCol);
		ellipse(330, pitch.width + 40 - r/2, r, r);
		stroke(threatCol);
		ellipse(330, pitch.width + 56 - r/2, r, r);
		stroke(supportCol);
		ellipse(490, pitch.width + 40 - r/2, r, r);
		stroke(receiverCol);
		ellipse(490, pitch.width + 56 - r/2, r, r);
	}
	
	public void draw(){
		world.update(sw.getElapsedTime());
		background(60,160,60);
		translate(offsetX,offsetY);
		pushMatrix();

		world.draw();
		
		textFont(font);
		if(showStates)
			drawStateInfo();
		if(showTypes)
			drawTeamInfo();
		drawLegend();
		popMatrix();
	}

	public class Pitch extends Artefact implements Constants {

		World world;
		PApplet app;
		PFont font0, font1;

		float length = (float) PITCH_LENGTH;
		float width = (float) PITCH_WIDTH;

		StopWatch sw = new StopWatch();

		Ball ball;

		Team[] teams = new Team[2];
		int[][] teamColors;
		String[] teamNames;
		float[] nameWidths;
		int[] teamScore = new int[2];
		int[] teamColorIdx = {-1, -1};

		boolean showSpots = false;
		
		// Pitch regions for players and region IDs for each team
		Domain[] regions;
		int[][] playerRegionIDs = { 
				{ 1,3,6,5,8,1,4,12,8,14 },		// pitch side 0
				{ 16,12,9,14,11,16,9,3,13,5 }	// pitch side 1
		};

		// Supporting spots on pitch
		Vector2D[][] sspots = new Vector2D[2][30];

		boolean ballInPlay = false;
		boolean playersOffPitch = true;

		public Pitch(PApplet papp, World world, float length, float width){
			super(null, null, length,  width);
			app = papp;
			font0 = app.createFont("Sans Serif", 24);
			font1 = app.createFont("Sans Serif", 18);
			this.world = world;
			
			addFSM();
			createRegions();
			createSupportingSpotPositions();

			teamColors = new int[6][];
			teamColors[0] = new int[] { app.color(255,0,0), app.color(255,200,200), app.color(96,0,0), app.color(128,0,0) };
			teamColors[1] = new int[] { app.color(0,0,255), app.color(200,200,255), app.color(0,0,96), app.color(0,0,128) };
			teamColors[2] = new int[] { app.color(255,0,255), app.color(255,200,255), app.color(96,0,96), app.color(128,0,128) };
			teamColors[3] = new int[] { app.color(255,128,0), app.color(255,192,64), app.color(166,82,0), app.color(238,118,0) };
			teamColors[4] = new int[] { app.color(0,255,255), app.color(200,255,255), app.color(0,96,96), app.color(0,128,128) };
			teamColors[5] = new int[] { app.color(230), app.color(190), app.color(96), app.color(128) };

			teamNames = new String[] {
					"Red Socks", "Blue Bottles", "Purple Hearts", "Orange Pips", "Cyan Eyeds", "Grey Beards"
			};
			nameWidths = new float[teamNames.length];
			app.textFont(font0);
			for(int i = 0; i < teamNames.length; i++)
				nameWidths[i] = app.textWidth(teamNames[i]);

			createBall();
			world.add(ball);

			teams[0] = new Team(app, this, world, 0);
			teams[1] = new Team(app, this, world, 1);

			newTeams();
			
			// Create the renderer for the pitch
			renderer(new PitchRenderer(app, "ai4g/grass.jpg"));

			addFSM();
			FSM().setGlobalState(PitchGlobal.state());
			world.add(this);
		}

		/**
		 * Make sure we have two different teams
		 */
		public void newTeams(){
			int t0 = teamColorIdx[0];
			int t1 = teamColorIdx[1];
			int nt0, nt1;
			do {
				nt0 = (int) app.random(teamColors.length);
			} while(nt0 == t0 || nt0 == t1);
			do {
				nt1 = (int) app.random(teamColors.length);
			} while(nt1 == nt0 || nt1 == t0 || nt1 == t1);
			teamColorIdx[0] = nt0;
			teamColorIdx[1] = nt1;
			teams[0].setTeamColors(teamColors[teamColorIdx[0]]);
			teams[1].setTeamColors(teamColors[teamColorIdx[1]]);
			teamScore[0] = teamScore[1] = 0;
		}

		private void createRegions(){
			regions = new Domain[18];
			double sizex = PITCH_LENGTH/6;
			double sizey = PITCH_WIDTH/3;
			double px, py;
			for(int i = 0; i < regions.length; i++){
				px = (i/3) * sizex;
				py = (i%3) * sizey;
				regions[i] = new Domain(px,py,px+sizex,py+sizey);
			}
		}

		public Domain getRegion(int nbr){
			return regions[nbr];
		}

		public int getRegionID(int side, int playerNo, int teamState){
			int p = (teamState == ATTACKING_STATE) ? 5 : 0;
			return playerRegionIDs[side][playerNo + p];
		}

		private void createSupportingSpotPositions() {
			double deltaX = PITCH_LENGTH * 0.5 * 0.12;
			double deltaY = PITCH_WIDTH * 0.12;
			double startX = PITCH_LENGTH * 0.5 * 0.26;
			double startY = PITCH_WIDTH * 0.20;
			for(int sp = 0; sp < sspots[0].length; sp++){
				int r = sp / 5;
				int c = sp % 5;
				sspots[1][sp] = new Vector2D(startX + c * deltaX, startY + r * deltaY);
				sspots[0][sp] = new Vector2D(PITCH_LENGTH - sspots[1][sp].x, PITCH_WIDTH - sspots[1][sp].y);
			}
		}

		public Team getTeam(int pitch_side){
			return teams[pitch_side];
		}

		public boolean isValidBallPos(Vector2D p){
			double br = ball.colRadius();
			return p.x - br > 0 && p.x + br < PITCH_LENGTH && p.y - br > 0 && p.y + br < PITCH_WIDTH;
		}

		private void createBall(){
			ball = new Ball(this);
			ball.renderer(new CirclePic(app, 2 * BALL_RADIUS, app.color(0), app.color(255), 1.2f));
			ball.Z(-2);
			ball.toCentreSpot(PITCH_LENGTH/2, PITCH_WIDTH/2);
			ball.toCentreSpot(PITCH_LENGTH/2, PITCH_WIDTH/2);
			world.add(ball);
		}

		// Called from Game class
		public void setTeamColors(int team, int col){
			teamColorIdx[team] = col;
			teams[team].setTeamColors(teamColors[col]);
		}

		/**
		 * 
		 * The pitch renderer implemented as an inner class.
		 * @author Peter Lager
		 *
		 */
		public class PitchRenderer extends PicturePS implements Constants {
			PImage grass;
			int lineCol;
			float lineWidth;

			public PitchRenderer(PApplet papp, String imgFilename) {
				super(papp);
				grass = app.loadImage(imgFilename);
				this.lineCol = app.color(255, 255, 255);
				lineWidth = 3;
			}

			@Override
			public void draw(BaseEntity owner, float posX, float posY, float velX,
					float velY, float headX, float headY) {
				app.pushStyle();
				app.pushMatrix();
				app.rectMode(PApplet.CORNER);
				app.ellipseMode(PApplet.CENTER);
				app.image(grass, 0, 0);
				app.noFill();
				app.stroke(lineCol);
				app.strokeWeight(lineWidth);
				app.rect(0,0, PITCH_LENGTH, PITCH_WIDTH);
				app.fill(lineCol);
				app.ellipse(PITCH_LENGTH/2, PITCH_WIDTH/2, 5, 5);
				app.noFill();
				app.ellipse(PITCH_LENGTH/2, PITCH_WIDTH/2, 160, 160);
				// Goals
				app.line(PITCH_LENGTH/2, 0, PITCH_LENGTH/2, PITCH_WIDTH);
				app.rect(0, GOAL_WIDTH, GOAL_DEPTH, GOAL_WIDTH);
				app.rect(PITCH_LENGTH - GOAL_DEPTH, GOAL_WIDTH, 30, GOAL_WIDTH);

				app.fill(teamColors[teamColorIdx[0]][0]);
				app.textFont(font0);
				app.text(teamNames[teamColorIdx[0]], 0, -30);
				app.textFont(font1);
				app.text(teamScore[0], 50, -8);

				app.fill(teamColors[teamColorIdx[1]][0]);
				app.textFont(font0);
				app.text(teamNames[teamColorIdx[1]], (float) (PITCH_LENGTH - nameWidths[teamColorIdx[1]]), -30);
				app.textFont(font1);
				app.text(teamScore[1], (float) (PITCH_LENGTH) - 50 - app.textWidth(""+ teamScore[1]), -8);

				if(showSpots)
					for(Team t : teams)
						t.drawSupportingSpots();

				String clockText;
				if(!playersOffPitch){
					app.noStroke();
					app.fill(255);
					float gtime = (float) sw.getRunTime();
					float d = TIMER_LENGTH  - TIMER_LENGTH * gtime / MATCH_TIME;
					app.rect((PITCH_LENGTH - TIMER_LENGTH)/2, -54, d, 7);
					app.noFill();
					app.stroke(0);
					app.strokeWeight(1);
					app.rect((PITCH_LENGTH - TIMER_LENGTH)/2, -54, TIMER_LENGTH, 7);

					int mins = (int) (gtime / 60);
					float secs = gtime - 60 * mins;
					clockText = PApplet.nf(mins, 2) + ":" + PApplet.nf(secs,2,1);
				}
				else
					clockText = "GAME OVER";

				app.fill(0);
				app.text(clockText, (float) (PITCH_LENGTH - app.textWidth(clockText))/2, -26);

				app.popMatrix();
				app.popStyle();		

			}

		}

	}

	public class Ball extends MovingEntity implements Constants {

		Pitch pitch;
		PlayerBase owner = null;

		PlayerBase lastkickedBy = null;
		long lastKickedAt = 0;

		double lowGoalY, highGoalY;

		public Ball(Pitch pitch) {
			super("ball", 
					new Vector2D(PITCH_LENGTH/2, PITCH_WIDTH/2),	// Position on pitch
					BALL_RADIUS, 									// Collision radius
					new Vector2D(0,0), 								// Velocity
					Float.MAX_VALUE, 								// Maximum velocity
					new Vector2D(0,1), 								// Heading
					BALL_MASS,										// Ball mass
					1, 												// Max turning rate
					Float.MAX_VALUE									// Maximum force
			);
			this.pitch = pitch;
		}

		public void kick(PlayerBase kicker, Vector2D dir, double force){
			lastkickedBy = kicker;
			lastKickedAt = System.currentTimeMillis();	
			dir.normalize();
			dir.mult(force/mass);
			velocity.set(dir);
			owner = null;
		}

		public boolean readyToBeKickedBy(PlayerBase kicker){
			if(lastkickedBy == kicker && System.currentTimeMillis() - lastKickedAt  < PlayerKickInterval)
				return false;	
			return true;
		}

		public PlayerBase getLastKickedBy(){
			return lastkickedBy;
		}

		public double timeToCoverDistance(Vector2D from, Vector2D to, double force){
			//this will be the velocity of the ball in the next time step *if*
			//the player was to make the pass. 
			double speed = force / mass;

			//calculate the velocity at B using the equation
			//
			//  v^2 = u^2 + 2as
			//

			//first calculate s (the distance between the two positions)
			double DistanceToCover =  Vector2D.dist(from, to);

			double term = speed * speed + 2.0 * DistanceToCover * FRICTION_MAG;

			//if  (u^2 + 2as) is negative it means the ball cannot reach point B.
			if (term <= 0.0) return -1.0;

			double v = Math.sqrt(term);

			//it IS possible for the ball to reach B and we know its speed when it
			//gets there, so now it's easy to calculate the time using the equation
			//
			//    t = v-u
			//        ---
			//         a
			//
			return (v-speed)/FRICTION_MAG;		
		}

		public Vector2D futurePosition(double time){
			//using the equation s = ut + 1/2at^2, where s = distance, a = friction
			//u=start velocity

			// start by calculating the ut term, which is a vector
			Vector2D s = Vector2D.mult(velocity, time);

			//calculate the 1/2at^2 term, which is scalar
			double half_a_t_squared = 0.5 * FRICTION_MAG * time * time;

			//turn the scalar quantity into a vector by multiplying the value with
			//the normalized velocity vector (because that gives the direction)
			Vector2D dir = Vector2D.normalize(velocity);
			dir.mult(half_a_t_squared);

			// Calculate the estimated position
			s.add(dir);
			//the predicted position is the balls position plus these two terms
			return s;
		}

		public void trap(PlayerBase owner){
			this.owner = owner;
			velocity.set(Vector2D.ZERO);
		}

		public void realease(){
			owner = null;
		}

		public boolean keeperHasBall(){
			return owner != null && owner.isGoalKeeper();
		}

		public void toCentreSpot(double x, double y){
			pos.set(x,y);
			velocity.set(0,0);
			heading.set(0,1);
		}

		public void update(double deltaTime, World world) {
			super.update(deltaTime, world);
			if(velocity.lengthSq() > deltaTime * FRICTION_MAG_SQ){
				Vector2D decel = Vector2D.normalize(velocity);
				decel.mult(deltaTime * FRICTION_MAG);
				velocity.add(decel);
			}
			else
				velocity.set(0,0);
			// Now check edge conditions
			boolean goalScored = false;
			if(pos.x - BALL_RADIUS < 0){
				velocity.x = Math.abs(velocity.x);
				goalScored = pos.y >= GOAL_LOW_Y && pos.y <= GOAL_HIGH_Y;
			}
			else if(pos.x + BALL_RADIUS > PITCH_LENGTH){
				velocity.x = -Math.abs(velocity.x);
				goalScored = pos.y >= GOAL_LOW_Y && pos.y <= GOAL_HIGH_Y;			
			}
			if(pos.y - BALL_RADIUS < 0)
				velocity.y = Math.abs(velocity.y);
			else if(pos.y + BALL_RADIUS > PITCH_WIDTH)
				velocity.y = -Math.abs(velocity.y);
			if(goalScored){
				velocity.set(0,0);
				int teamNo = pos.x < PITCH_LENGTH/2 ? 1 : 0;
				Dispatcher.dispatch(0, entityID, pitch.ID(), GOAL_SCORED, teamNo);
			}
		}


	}

	public class Team extends Artefact implements Constants {
		// Determines which side of the pitch is their side
		// LEFT = 0 # RIGHT = 1
		int pitchSide;
		Vector2D teamHeading;

		protected PApplet app;
		protected Pitch pitch;
		Goal goal;
		
		int[] teamColors;

		PlayerBase[] players = new PlayerBase[5];
		int nbrReachedTarget = 5;

		private PlayerBase controllingPlayer;
		private PlayerBase receivingPlayer;
		private PlayerBase supportingPlayer;
		private PlayerBase playerClosestToBall;

		double distToBallSq = Double.MAX_VALUE;

		double[] sspotScores = new double[30];
		final Vector2D[] sspotPositions;
		Vector2D bestSupportingPosition = null;

		long timeSinceSupportCalculated;

		Vector2D[] passes = new Vector2D[6];

		public boolean isInControl(){
			return controllingPlayer != null;
		}

		public void calculateClosestPlayerToBall(){
			distToBallSq = Double.MAX_VALUE;
			for(PlayerBase p : players){
				double d2 = Vector2D.distSq(p.pos(), pitch.ball.pos());
				p.distToBallSq = d2;
				if(d2 < distToBallSq){
					distToBallSq = d2;
					playerClosestToBall = p;
				}
			}
		}

		public void setControllingPlayer(PlayerBase player){
			// If we are taking control then the opposing team are losing it.
			if(player != null){
				getOtherTeam().controllingPlayer = null;
				//			getOtherTeam().teamLosesControl();
			}
			resetSupportSpotScores();
			controllingPlayer = player;
		}

		public PlayerBase getControllingPlayer(){
			return controllingPlayer;
		}

		/**
		 * @return the receivingPlayer
		 */
		public PlayerBase getReceivingPlayer() {
			return receivingPlayer;
		}

		/**
		 * @param receivingPlayer the receivingPlayer to set
		 */
		public void setReceivingPlayer(PlayerBase receivingPlayer) {
			this.receivingPlayer = receivingPlayer;
		}

		/**
		 * @return the supportingPlayer
		 */
		public PlayerBase getSupportingPlayer() {
			return supportingPlayer;
		}

		/**
		 * @param supportingPlayer the supportingPlayer to set
		 */
		public void setSupportingPlayer(PlayerBase supportingPlayer) {
			this.supportingPlayer = supportingPlayer;
		}

		/**
		 * @return the playerClosestToBall
		 */
		public PlayerBase getPlayerClosestToBall() {
			return playerClosestToBall;
		}

		/**
		 * @param playerClosestToBall the playerClosestToBall to set
		 */
		public void setPlayerClosestToBall(PlayerBase playerClosestToBall) {
			this.playerClosestToBall = playerClosestToBall;
		}

		public void resetSupportSpotScores(){
			for(int sp = 0; sp < sspotPositions.length; sp++)
				sspotScores[sp] = 1.0;
			// This will force a recalculation if a new sopporting player is calculated
			timeSinceSupportCalculated = -SupportSpotUpdateInterval;
		}

		public Vector2D getBestSupportngSpot(){
			long currTime = System.currentTimeMillis();
			// If we have not calculated the supporting spot 
			if(bestSupportingPosition == null || currTime - timeSinceSupportCalculated >= SupportSpotUpdateInterval){
				bestSupportingPosition = new Vector2D();
				timeSinceSupportCalculated = currTime;
				double bestScoreSoFar = 0.0;
				for(int sp = 0; sp < sspotPositions.length; sp++){
					sspotScores[sp] = 1.0;
					if(controllingPlayer != null){
						// Is it safe to pass to this spot
						if(isPassSafeFromAllOpponents(controllingPlayer.pos(), sspotPositions[sp], null, MaxPassingForce))
							sspotScores[sp] += Spot_CanPassScore;
						// Is it possible to take a shot at goal from this position
						if(canShoot(controllingPlayer.pos(), MaxShootingForce, null))
							sspotScores[sp] += Spot_CanScoreFromPositionScore;
						// If we have a supporting player see how far away from supporting spot
						if(supportingPlayer != null){
							final double optimalDistance = 200;
							double dist = Vector2D.dist(controllingPlayer.pos(), sspotPositions[sp]);
							double temp = Math.abs(optimalDistance - dist);
							if(temp < dist)
								sspotScores[sp] += Spot_DistFromControllingPlayerScore * (optimalDistance - temp) / optimalDistance;
						}
					}
					if(sspotScores[sp] > bestScoreSoFar){
						bestScoreSoFar = sspotScores[sp];
						bestSupportingPosition.set(sspotPositions[sp]);
					}
				}
			}
			return bestSupportingPosition;
		}

		/**
		 * Get the best supporting player
		 * @return
		 */
		public PlayerBase getBestSupportingAttacker(){
			double closestSoFar = Double.MAX_VALUE;
			PlayerBase bestPlayer = null;
			for(int i = 1; i < players.length; i++){
				if(players[i].getPlayerType() == ATTACKER && players[i] != controllingPlayer){
					double dist = Vector2D.distSq(players[i].pos(), getBestSupportngSpot());
					if(dist < closestSoFar){
						closestSoFar = dist;
						bestPlayer = players[i];
					}
				}
			}
			return bestPlayer;
		}

		/**
		 * Find the best pass target position and a receiver.
		 * 
		 * @param passer the player with the ball
		 * @param passTarget the target position to calculate
		 * @param power
		 * @param minPassingDistance depends on whether a goalie or field player
		 * @return null if can't find a good pass
		 */
		public PlayerBase findBestPass(PlayerBase passer, Vector2D passTarget, double power, double minPassingDistance){
			double closestToGoalSoFar = Double.MAX_VALUE;
			double minPassingDistanceSq = minPassingDistance * minPassingDistance;
			PlayerBase receiver = null;
			Vector2D target = new Vector2D();
			// Prevent  passing back to goalie from opponents half
			int startI = passer.isInHotHalf() ? 1 : 0; 
			for(int i = startI; i < players.length; i++){
				if(getBestPassToReceiver(passer, players[i], target, power)){
					if(players[i] != passer && Vector2D.distSq(passer.pos(), players[i].pos()) > minPassingDistanceSq){
						double distToGoalLine = Math.abs(target.x - getOtherGoalCenter().x);
						if(distToGoalLine < closestToGoalSoFar){
							closestToGoalSoFar = distToGoalLine;
							receiver = players[i];
							passTarget.set(target);
						}
					}
				}
			}
			return receiver;
		}

		/**
		 * Calculates the best pass direction to another player. Returns true if it finds one
		 * @param passer
		 * @param receiver
		 * @param passTarget
		 * @param power
		 * @return
		 */
		private boolean getBestPassToReceiver(PlayerBase passer, PlayerBase receiver, Vector2D passTarget, double power) {
			// Get the receiver and ball position since we use these a lot
			Vector2D ballPos = pitch.ball.pos();
			Vector2D receiverPos = receiver.pos();
			// Calculate the time for the ball to reach receiver if he doesn't move
			double time = pitch.ball.timeToCoverDistance(ballPos, receiverPos, power);
			// If less than zero then can't reach
			if(time < 0) return false;

			// Calculate the intercept range for the receiver
			double interceptRange = time * receiver.maxSpeed();
			double scalingFactor = 0.3;
			interceptRange *= scalingFactor;

			// Calculate the intercept points either side of the receiver (returns 0/2/4 points
			double[] pts = Geometry2D.tangents_to_circle(ballPos.x, ballPos.y, receiverPos.x, receiverPos.y, interceptRange);
			int nbrPassesToTest = pts.length / 2 + 1;
			// Get an array of points to test
			switch(nbrPassesToTest){
			case 1:
				passes[0] = receiverPos;
				break;
			case 2:
				passes[0] = new Vector2D(pts[0], pts[1]);
				passes[1] = receiverPos;
				break;
			case 3:
				passes[0] = new Vector2D(pts[0], pts[1]);
				passes[1] = new Vector2D(pts[2], pts[3]);
				passes[2] = receiverPos;
				break;
			default:
				System.out.println("Team: getBestPassToReceiver() error"); 		
			}
			
			// this pass is the best found so far if it is:
			//
			//  1. Further upfield than the closest valid pass for this receiver
			//     found so far
			//  2. Within the playing area
			//  3. Cannot be intercepted by any opponents

			double closestSoFar = Float.MAX_VALUE;
			boolean bResult = false;

			for(int i = 0; i < nbrPassesToTest; i++){
				double dist = Math.abs(passes[i].x - getOtherGoalCenter().x);
				if(dist < closestSoFar && pitch.isValidBallPos(passes[i]) &&
						isPassSafeFromAllOpponents(ballPos, passes[i], receiver, power)){
					closestSoFar = dist;
					passTarget.set(passes[i]);
					bResult = true;
				}
			}
			return bResult;
		}

		private boolean isPassSafeFromOpponent(Vector2D from, Vector2D target, PlayerBase receiver, PlayerBase opp, double passingForce) {
			Vector2D toTarget = Vector2D.sub(target, from);
			toTarget.normalize();
			Vector2D perp = toTarget.getPerp();
			Vector2D localPosOpp = Transformations.pointToLocalSpace(opp.pos(), toTarget, perp, from);
			if(localPosOpp.x < 0)
				return true;

			if(Vector2D.distSq(from, target) < Vector2D.distSq(opp.pos(), from)){
				if(receiver != null)
					return (Vector2D.distSq(target, opp.pos()) > Vector2D.distSq(target, receiver.pos()));
				else 
					return true;
			}
			double timeForBall = pitch.ball.timeToCoverDistance(Vector2D.ZERO, new Vector2D(localPosOpp.x, 0), passingForce);
			double reach = opp.maxSpeed() * timeForBall + pitch.ball.colRadius() + opp.colRadius();

			return (Math.abs(localPosOpp.y) >= reach);
		}

		private boolean isPassSafeFromAllOpponents(Vector2D from, Vector2D target,
				PlayerBase receiver, double passingForce) {
			PlayerBase[] opponents = getOtherPlayers();
			for(PlayerBase opp : opponents){
				if(!isPassSafeFromOpponent(from, target, receiver, opp, passingForce))
					return false;
			}
			return true;
		}

		public void requestPass(PlayerBase player){
			// Only 10% chance of request sent
			if(Math.random() > 0.01 || controllingPlayer == null)
				return;
			if(isPassSafeFromAllOpponents(controllingPlayer.pos(), player.pos(), player, MaxPassingForce)){
				Dispatcher.dispatch(0, player.ID(), controllingPlayer.ID(), PASS_TO_ME, player);
			}
		}

		boolean canShoot(Vector2D fromPos, double force, Vector2D shotTarget) {
			int numAttempts = NumAttemptsToFindValidStrike;

			Vector2D target = new Vector2D(pitchSide == 0 ? PITCH_LENGTH : 0, 0);
			double minY = (PITCH_WIDTH - GOAL_WIDTH)/2 + pitch.ball.colRadius();
			double maxY = (PITCH_WIDTH + GOAL_WIDTH)/2 - pitch.ball.colRadius();

			while(numAttempts-- > 0){
				target.y = MathUtils.randomInRange(minY, maxY);
				double time = pitch.ball.timeToCoverDistance(fromPos, target, force);
				if(time >= 0 && isPassSafeFromAllOpponents(fromPos, target, null, force)){
					if(shotTarget == null)
						shotTarget = new Vector2D(target);
					else
						shotTarget.set(target);
					return true;
				}
			}
			return false;
		}

		void addNoiseToKick(Vector2D ballPos, Vector2D ballTarget) {
			double displacement = (Math.PI - Math.PI*PlayerKickingAccuracy) * MathUtils.randomClamped();
			Vector2D toTarget = Vector2D.sub(ballTarget, ballPos);
//			MathUtils.rotateVectorAboutOrigin(toTarget, displacement);
			toTarget = Transformations.vec2DRotateAroundOrigin(toTarget, displacement);
			toTarget.add(ballPos);
			ballTarget.set(toTarget);
		}


		public boolean isOpponentWithinRadius(Vector2D pos, double rad){
			PlayerBase[] opponents = getOtherPlayers();
			double radSq = rad * rad;
			for(PlayerBase opp : opponents){
				if(Vector2D.distSq(pos, opp.pos()) < radSq)
					return true;
			}
			return false;		
		}

		public Team getOtherTeam(){
			return pitch.getTeam(1-pitchSide);
		}

		public PlayerBase[] getOtherPlayers(){
			return getOtherTeam().players;
		}

		public Vector2D getOwnGoalCenter(){
			return goal.centre;
		}

		public Vector2D getOtherGoalCenter(){
			return getOtherTeam().goal.centre;
		}

		public Goal getOtherGoal(){
			return getOtherTeam().goal;
		}

		/**
		 * Change region to use depending on whether the team is attacking or defending.
		 * It does not order the players back to these regions. 
		 * @param stateID
		 */
		public void changePlayerHomePositions(int stateID){
			for(PlayerBase p : players)
				p.changePlayerHomePositions(stateID);
		}

		public void sendTeamOffPitch(){
			controllingPlayer = null;
			playerClosestToBall = null;
			receivingPlayer = null;
			supportingPlayer = null;
			nbrReachedTarget = 0;
			FSM().changeState(TeamDefending.state());
			
			for(int i = 0; i < players.length; i++){
				players[i].FSM().changeState(PlayerLeavesPitch.state());
				//Dispatcher.dispatch( (i == 0) ? 500 : 0, entityID, players[i].ID(), GO_OFF_PITCH);
			}		
		}

		public void sendFieldPlayersToRegion(){
			for(int i = 1; i < players.length; i++){
				players[i].FSM().changeState(PlayerReturnToRegion.state());
			}
		}

		public boolean allPlayersReachedTarget(){
			return nbrReachedTarget == players.length;
		}

		public Team(PApplet papp, Pitch pitch, World world, int pitch_side){
			addFSM();

			this.pitchSide = pitch_side;
			this.app = papp;
			this.pitch = pitch;

			timeSinceSupportCalculated = System.currentTimeMillis();
			sspotPositions = pitch.sspots[pitchSide];
//			sspotPositions = pitch.getSupportingSpots(pitchSide);
			for(int sp = 0; sp < sspotPositions.length; sp++)
				sspotScores[sp] = 1;

			// Calculate the team heading 
			int dir = (1 - 2 * pitchSide);
			teamHeading = new Vector2D(dir,0);

			// Create goal
			goal = new Goal(papp, this, pitch, pitch_side);
			goal.Z(-5);
			world.add(goal);

//			// Create players
			int x = (int) (PITCH_LENGTH/2 - dir * 20);
			players[0] = new GoalKeeper("0", 
					new Vector2D(x, PITCH_WIDTH + 10),	// Position
					PLAYER_RADIUS,						// Collision radius
					new Vector2D(0,0),					// Velocity
					KeeperMaxSpeedWithoutBall,			// Maximum velocity
					offPitchHeading,					// Heading
					PLAYER_MASS,						// Mass
					PlayerMaxTurnRate,					// Max turn rate
					PlayerMaxForce,						// Max force
					pitchSide,							// 0 or 1
					this,								// the players team
					0									// player position in team
			);

			for(int i = 1; i < players.length; i++)
				players[i] = new FieldPlayer("" + i, 
						new Vector2D(x, PITCH_WIDTH + 15 + i * 50),	// Position
						PLAYER_RADIUS,						// Collision radius
						new Vector2D(0,0),					// Velocity
						PlayerMaxSpeedWithoutBall,			// Maximum velocity
						offPitchHeading,					// Heading
						PLAYER_MASS,						// Mass
						PlayerMaxTurnRate,					// Max turn rate
						PlayerMaxForce,						// Max force
						pitchSide,							// 0 or 1
						this,								// the players team
						i									// player position in team
				);
			for(int i = 0; i < players.length; i++){
				world.add(players[i]);
			}
			FSM().setGlobalState(TeamGlobal.state());
			FSM().setCurrentState(TeamDefending.state());
			world.add(this);
		}

		public void setTeamColors(int[] cols){
			teamColors = cols;
			PersonPic ppic = new PersonPic(app, 2 * PLAYER_RADIUS, teamColors[1], teamColors[2], teamColors[0], 1);
			for(int i = 0; i < players.length; i++)
				players[i].renderer(ppic);
		}

		public void drawSupportingSpots(){
			if(controllingPlayer != null){
			app.noStroke();
			app.fill(teamColors[3]);
			for(int sp = 0; sp < sspotPositions.length; sp++)
				app.ellipse((float)sspotPositions[sp].x, (float)sspotPositions[sp].y, 
						2+(float)sspotScores[sp], 2+(float)sspotScores[sp]);
			}
		}

	}

	public class GoalKeeper extends PlayerBase implements Constants {

		Vector2D rearInterposeTarget = new Vector2D();
		
		public GoalKeeper(String name, Vector2D position, double radius,
				Vector2D velocity, double max_speed, Vector2D heading, double mass,
				double max_turn_rate, double max_force, int side, Team team, int playerNumber) {
			
			super(name, position, radius, velocity, max_speed, heading, mass,
					max_turn_rate, max_force, side, team, playerNumber);
			FSM().setGlobalState(KeeperGlobal.state());
		}
		
		public boolean isBallWithinRange(){
			return Vector2D.distSq(pos, team.pitch.ball.pos()) <= KeeperInBallRangeSq;
		}

		public boolean isBallWithinInterceptRange(){
			return Vector2D.distSq(team.getOwnGoalCenter(), team.pitch.ball.pos()) <= GoalKeeperInterceptRangeSq;
		}

		public boolean tooFarFromGoalMouth(){
			return Vector2D.distSq(pos, getRearInterposeTarget()) > GoalKeeperInterceptRangeSq;
		}

		public Vector2D getRearInterposeTarget(){
			double x = team.goal.centre.x;
			double y = (PITCH_WIDTH - GOAL_WIDTH)/2 + (team.pitch.ball.pos().y * GOAL_WIDTH / PITCH_WIDTH);
			rearInterposeTarget.set(x,y);
			return rearInterposeTarget;
		}
		
		public boolean isGoalKeeper(){
			return true;
		}
		
		public boolean isFieldPlayer(){
			return false;
		}


	}
	public abstract class PlayerBase extends Vehicle implements Constants {

		// These are fixed for the duration
		final Team team;
		final int type;
		final int number;
		
		// These need to be changed if the pitch side changes
		int defendRegion;
		int attackRegion;
		int regionToUse;
		// this also depends on pitch side
		Vector2D offFieldPosition = new Vector2D();
		
		Vector2D targetPos = new Vector2D();

		double distToBallSq = Double.MAX_VALUE;

		public boolean isControllingPlayer(){
			return this == team.getControllingPlayer();
		}
		
		public boolean isBallWithinReceivingRange(){
			return Vector2D.distSq(pos, team.pitch.ball.pos()) < BallWithinReceivingRangeSq;
		}
		
		public boolean isBallWithinKickingRange(){
			return(Vector2D.distSq(pos, team.pitch.ball.pos()) <= PlayerKickingDistanceSq);
		}
		
		public boolean isClosestTeamMemberToBall(){
			return this == team.getPlayerClosestToBall();
		}

		public boolean isPositionInFrontOfPlayer(Vector2D p){
			Vector2D toSubject = Vector2D.sub(p, pos);
			return toSubject.dot(heading) > 0;
		}
		
		public boolean isThreatened(){
			PlayerBase[] opponents = team.getOtherPlayers();
			for(PlayerBase opponent : opponents){
				if(isPositionInFrontOfPlayer(opponent.pos()) &&
						Vector2D.distSq(pos, opponent.pos()) < PlayerComfortZoneSq)
					return true;
			}
			return false;
		}
		
		public void findSupport(){
			// If there is no current supporting player get one.
			if(team.getSupportingPlayer() == null){
				team.setSupportingPlayer(team.getBestSupportingAttacker());
				Dispatcher.dispatch(0, entityID, team.getSupportingPlayer().entityID, SUPPORT_ATTACKER);
			}
			else { // We have a supporting player See if we have have a new best supporting player
				PlayerBase bsp = team.getBestSupportingAttacker();
				// is this a new supporting player
				if(bsp != null && bsp != team.getSupportingPlayer()){
					// Send old supporting player home
					Dispatcher.dispatch(0, entityID, team.getSupportingPlayer().entityID, GO_TO_REGION);
					//Send message for player to take over supporting position
					Dispatcher.dispatch(0, entityID, bsp.entityID, SUPPORT_ATTACKER);
					// Set new supporting player and
					team.setSupportingPlayer(bsp);
				}
			}
		}
		
		// Is in the opponents third of the pitch
		public boolean isInHotRegion(){
			return Math.abs(team.goal.centre.x - pos.x) > PITCH_LENGTH * 0.60;
		}
		
		public boolean isInHotHalf(){
			return Math.abs(team.goal.centre.x - pos.x) > PITCH_LENGTH * 0.45;
		}
		
		public boolean isAheadOfAttacker(){
			if(team.getControllingPlayer() == null)
				return false;
			double goalx = team.getOtherGoalCenter().x;
			return Math.abs(pos.x - goalx) < Math.abs(team.getControllingPlayer().pos().x - goalx); 
		}
		
		public boolean isClosestPlayerOnPitchToBall(){
			return (this == team.getPlayerClosestToBall() && distToBallSq < team.getOtherTeam().distToBallSq);
		}

		public boolean isAtTarget(){
			return (Vector2D.distSq(pos, targetPos) <  PlayerAtTargetRangeSq);
		}
		
		public boolean isNearTarget(){
			return (Vector2D.distSq(pos, this.targetPos) <  PlayerNearTargetRangeSq);
		}
		
		public void trackBall(){
			Vector2D ballPos = team.pitch.ball.pos();
			headingAtRest(Vector2D.sub(ballPos, pos));
		}
		
		public abstract boolean isGoalKeeper();
		
		public abstract boolean isFieldPlayer();
		
		public void changePlayerHomePositions(int stateID){
			if(stateID == DEFENDING)
				regionToUse = defendRegion;
			else
				regionToUse = attackRegion;
		}

		public int getPlayerType(){
			return type;
		}
		
		public PlayerBase(String name, Vector2D position, double radius,
				Vector2D velocity, double max_speed, Vector2D heading, double mass,
				double max_turn_rate, double max_force, int side, Team team, int playerNumber) {
			
			super(name, position, radius, velocity, max_speed, heading, mass,
					max_turn_rate, max_force);
			addFSM();
			this.team = team;
			this.number = playerNumber;
			defendRegion = team.pitch.getRegionID(side, number, DEFENDING_STATE);
			attackRegion = team.pitch.getRegionID(side, number, ATTACKING_STATE);
			
			switch(number){
			case 0:
				type = GOALKEEPER;
				break;
			case 1:
			case 2:
				type = DEFENDER;
				break;
			default:
				type = ATTACKER;
			}
			offFieldPosition.set(position);
			headingAtRest(heading);
			FSM().setCurrentState(PlayerWait.state());
			targetPos.set(position);
			AP(new AutoPilot());
		}

	}

	public class FieldPlayer extends PlayerBase {

		public FieldPlayer(String name, Vector2D position, double radius,
				Vector2D velocity, double max_speed, Vector2D heading, double mass,
				double max_turn_rate, double max_force, int side, Team team, int playerNumber) {
			
			super(name, position, radius, velocity, max_speed, heading, mass,
					max_turn_rate, max_force, side, team, playerNumber);
			FSM().setGlobalState(PlayerGlobal.state());
		}
		
		public boolean isGoalKeeper(){
			return false;
		}
		
		public boolean isFieldPlayer(){
			return true;
		}

	}

	public class Goal extends Artefact implements Constants {

		public final Vector2D centre;
		
		public Goal(PApplet papp, Team team, Pitch pitch, int pitchSide){
			double x = (pitchSide == 0) ? KEEPER_TEND_DIST : PITCH_LENGTH - KEEPER_TEND_DIST;
			double y = PITCH_WIDTH/2;
			pos.set(x,y);
			centre = new Vector2D(x,y);

			this.renderer( new GoalRenderer(papp, team, GOAL_WIDTH, GOAL_DEPTH));

		}
		
		public class GoalRenderer extends PicturePS {

			Team team;
			
			float depth = 0;
			float width = 0;
			
			public GoalRenderer(PApplet papp, Team team, double width, double depth){
				super(papp);
				this.team = team;
				this.width = (float) width;
				this.depth = (float) depth;
			}
			
			
			@Override
			public void draw(BaseEntity owner, float posX, float posY, float velX,
					float velY, float headX, float headY) {
				app.pushStyle();
				app.pushMatrix();
				app.translate(posX, posY);
				
				app.rectMode(PApplet.CENTER);

				app.noFill();
				app.stroke(team.teamColors[0]);
				app.strokeWeight(2.0f);
				app.rect(0,0,depth, width);

				app.popMatrix();
				app.popStyle();	
			}
		}
	}

	static public class PitchGameOn extends State implements Constants {

		private static PitchGameOn instance = null;	

		public static PitchGameOn state(){
			if(instance == null){
				instance = new PitchGameOn();
				instance.name = "Game On";
			}
			return instance;
		}
	
		@Override
		public void enter(BaseEntity owner) {
			Pitch pitch = (Pitch) owner;
			pitch.ballInPlay = true;
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
		}

		@Override
		public void exit(BaseEntity owner) {
			Pitch pitch = (Pitch) owner;
			pitch.ballInPlay = false;
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			Pitch pitch = (Pitch) owner;
			switch(tgram.msg){
			case TEAMS_PREPARE_FOR_KICK_OFF:
				pitch.FSM().changeState(PitchPrepareForKickOff.state());
				return true;
			case GOAL_SCORED:
				int teamToScore = (Integer) tgram.extraInfo[0];
				pitch.teamScore[teamToScore]++;
				pitch.FSM().changeState(PitchPrepareForKickOff.state());
			}
			return false;
		}

	}

	static public class PitchGlobal extends State implements Constants {

		private static PitchGlobal instance = null;	

		public static PitchGlobal state(){
			if(instance == null){
				instance = new PitchGlobal();
				instance.name = "Game On";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Pitch pitch = (Pitch) owner;
			if(pitch.sw.getRunTime() >= MATCH_TIME){
				pitch.sw.reset();
				pitch.FSM().changeState(PitchPlayersOff.state());
			}
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public static class PitchPlayersOff extends State implements Constants {


		private static PitchPlayersOff instance = null;	

		public static PitchPlayersOff state(){
			if(instance == null){
				instance = new PitchPlayersOff();
				instance.name = "Players Off";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			Pitch pitch = (Pitch) owner;
			pitch.ballInPlay = false;
			pitch.playersOffPitch = true;
			pitch.teams[0].sendTeamOffPitch();
			pitch.teams[1].sendTeamOffPitch();
			pitch.ball.toCentreSpot(PITCH_LENGTH/2, PITCH_WIDTH/2);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Pitch pitch = (Pitch) owner;
			if(pitch.teams[0].allPlayersReachedTarget() && pitch.teams[1].allPlayersReachedTarget()){
				// Make sure we only do this once
				pitch.teams[0].nbrReachedTarget = 0;
				pitch.teams[1].nbrReachedTarget = 0;
				
				pitch.ball.toCentreSpot(PITCH_LENGTH/2, PITCH_WIDTH/2);
				// Select 2 new teams for the next match
				pitch.newTeams();
				Dispatcher.dispatch(2000, pitch.ID(), pitch.ID(), TEAMS_PREPARE_FOR_KICK_OFF);
			}
		}

		@Override
		public void exit(BaseEntity owner) {

		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			Pitch pitch = (Pitch) owner;
			switch(tgram.msg){
			case TEAMS_PREPARE_FOR_KICK_OFF:
				pitch.FSM().changeState(PitchPrepareForKickOff.state());
				return true;
			}
			return false;
		}
	}
	
	
	public static class PitchPrepareForKickOff extends State implements Constants{


		private static PitchPrepareForKickOff instance = null;	

		public static PitchPrepareForKickOff state(){
			if(instance == null){
				instance = new PitchPrepareForKickOff();
				instance.name = "Prepare for kickoff";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			Pitch pitch = (Pitch) owner;
			pitch.ballInPlay = false;
			pitch.teams[0].FSM().changeState(TeamPrepareForKickOff.state());
			pitch.teams[1].FSM().changeState(TeamPrepareForKickOff.state());
			pitch.ball.toCentreSpot(PITCH_LENGTH/2, PITCH_WIDTH/2);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Pitch pitch = (Pitch) owner;
			if(pitch.teams[0].allPlayersReachedTarget() && pitch.teams[1].allPlayersReachedTarget()){
				Dispatcher.dispatch(1000, pitch.ID(), pitch.ID(), TEAMS_READY_FOR_KICK_OFF);
			}
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			Pitch pitch = (Pitch) owner;
			switch(tgram.msg){
			case TEAMS_READY_FOR_KICK_OFF:
				if(pitch.FSM().isPreviousState(PitchPlayersOff.state()))
					pitch.sw.reset();
				pitch.playersOffPitch = false;
				pitch.FSM().changeState(PitchGameOn.state());
				return true;
			}
			return false;
		}

	}

	public static class TeamAttacking extends State implements Constants {


		private static TeamAttacking instance = null;
		
		public static TeamAttacking state(){
			if(instance == null){
				instance = new TeamAttacking();
				instance.name = "Attacking";
			}
			return instance;
		}
		
		@Override
		public void enter(BaseEntity owner) {
			Team team = (Team) owner;
			team.changePlayerHomePositions(ATTACKING);
			PlayerBase[] players = team.players;
			for(int i = 1; i < players.length; i++){
				if(players[i].FSM().isCurrentState(PlayerWait.state()))
						players[i].FSM().changeState(PlayerReturnToRegion.state());			
			}
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Team team = (Team) owner;
			if(!team.isInControl())
				team.FSM().changeState(TeamDefending.state());
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}


	public static class TeamDefending extends State implements Constants {


		private static TeamDefending instance = null;
		
		public static TeamDefending state(){
			if(instance == null){
				instance = new TeamDefending();
				instance.name = "Defending";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			Team team = (Team) owner;
			team.changePlayerHomePositions(DEFENDING);
			PlayerBase[] players = team.players;
			for(int i = 1; i < players.length; i++){
				if(players[i].FSM().isCurrentState(PlayerWait.state()))
						players[i].FSM().changeState(PlayerReturnToRegion.state());	
			}
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Team team = (Team) owner;
			if(team.isInControl())
				team.FSM().changeState(TeamAttacking.state());
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}


	public static class TeamGlobal extends State implements Constants{

		private static TeamGlobal instance = null;

		public static TeamGlobal state(){
			if(instance == null)
				instance = new TeamGlobal();
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Team team = (Team) owner;
			if(team.pitch.ballInPlay){
				team.calculateClosestPlayerToBall();
			}
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			Team team = (Team) owner;
			switch(tgram.msg){
			case AT_TARGET:
				if(team.nbrReachedTarget == team.players.length)
					team.nbrReachedTarget = 1;
				else
					team.nbrReachedTarget++;
				break;
			}
			return false;
		}

	}

	public static class TeamPrepareForKickOff extends State implements Constants{
		

		private static TeamPrepareForKickOff instance = null;
		
		public static TeamPrepareForKickOff state(){
			if(instance == null){
				instance = new TeamPrepareForKickOff();
				instance.name = "Prepare for kickoff";
			}
			return instance;
		}

		/** Implementing singleton pattern */
		private TeamPrepareForKickOff(){}
		
		@Override
		public void enter(BaseEntity owner) {
			Team team = (Team) owner;
			team.setControllingPlayer(null);
			team.setPlayerClosestToBall(null);
			team.setReceivingPlayer(null);
			team.setSupportingPlayer(null);
			team.nbrReachedTarget = 0;
			for(PlayerBase p : team.players){
				Dispatcher.dispatch(0, team.ID(), p.ID(), PLAYER_PREPARE_FOR_KICKOFF);
			}

		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			Team team = (Team) owner;
			if(team.allPlayersReachedTarget()) {
				team.FSM().changeState(TeamDefending.state());
			}
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public static class KeeperGlobal extends State implements Constants {

		private static KeeperGlobal instance = null;
		
		public static KeeperGlobal state(){
			if(instance == null){
				instance = new KeeperGlobal();
				instance.name = "Keeper global";
			}
			return instance;
		}
		
		@Override
		public void enter(BaseEntity owner) {
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			GoalKeeper keeper = (GoalKeeper) owner;
			switch(tgram.msg){
			case GO_TO_REGION:
				keeper.FSM().changeState(PlayerReturnToRegion.state());
				return true;
			case RECEIVE_BALL:
				keeper.FSM().changeState(KeeperInterceptBall.state());
				return true;
			case PLAYER_PREPARE_FOR_KICKOFF:
				keeper.FSM().changeState(PlayerPrepareForKickOff.state());
				return true;
			case TEND_GOAL:			
				keeper.FSM().changeState(KeeperTendGoal.state());
				break;
			}
			return false;
		}
	}

	public static class KeeperInterceptBall extends State implements Constants {

		private static KeeperInterceptBall instance = null;
		
		public static KeeperInterceptBall state(){
			if(instance == null){
				instance = new KeeperInterceptBall();
				instance.name = "Intercept ball";
			}
			return instance;
		}
		
		
		@Override
		public void enter(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.AP().pursuitOn(keeper.team.pitch.ball);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			GoalKeeper keeper = (GoalKeeper) owner;
			
			if(keeper.tooFarFromGoalMouth() && !keeper.isClosestPlayerOnPitchToBall()){
				keeper.FSM().changeState(KeeperReturnHome.state());
				return;
			}
			
			if(keeper.isBallWithinRange()){
				keeper.team.pitch.ball.trap(keeper);
				keeper.FSM().changeState(KeeperPutBallBackInPlay.state());
				return;
			}
			
			
		}

		@Override
		public void exit(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.AP().pursuitOff();
		
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}
		

	}

	
	public static class KeeperPutBallBackInPlay extends State implements Constants {

		private static KeeperPutBallBackInPlay instance = null;

		public static KeeperPutBallBackInPlay state(){
			if(instance == null){
				instance = new KeeperPutBallBackInPlay();
				instance.name = "Put ball into play";
			}
			return instance;
		}


		@Override
		public void enter(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.team.setControllingPlayer(keeper);
			keeper.team.FSM().changeState(TeamDefending.state());
			keeper.team.getOtherTeam().FSM().changeState(TeamDefending.state());
			keeper.team.sendFieldPlayersToRegion();
			keeper.team.getOtherTeam().sendFieldPlayersToRegion();
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			GoalKeeper keeper = (GoalKeeper) owner;
			Ball ball = keeper.team.pitch.ball;
			Vector2D ballPos = ball.pos();
			Vector2D ballTarget = new Vector2D();
 
			if(!keeper.isThreatened()){
				PlayerBase receiver = keeper.team.findBestPass(keeper, ballTarget, MaxPassingForce, KeeperMinPassDistance);
				if(receiver != null){
					keeper.team.addNoiseToKick(ballPos, ballTarget);
					Vector2D kickDirection = Vector2D.sub(ballTarget, ballPos);
					keeper.team.pitch.ball.kick(keeper, kickDirection, MaxPassingForce);
					Dispatcher.dispatch(0, keeper.ID(), receiver.ID(), RECEIVE_BALL, ballTarget);
					keeper.FSM().changeState(KeeperTendGoal.state());
					return;
				}
				else {
					// can't find receiver kick ball towards one of the defenders anyway
					// to prevent lockup
				}
			}
			keeper.velocity(Vector2D.ZERO);
		}

		@Override
		public void exit(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.team.pitch.ball.realease();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}


	}

	public static class KeeperReturnHome extends State implements Constants {

		private static KeeperReturnHome instance = null;
		
		public static KeeperReturnHome state(){
			if(instance == null){
				instance = new KeeperReturnHome();
				instance.name = "Return home";
			}
			return instance;
		}
		
		
		@Override
		public void enter(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.targetPos.set(keeper.team.pitch.getRegion(keeper.regionToUse).centre);
			keeper.targetPos.add(MathUtils.randomClamped(), MathUtils.randomClamped());
			keeper.AP().arriveOn(keeper.targetPos, SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			GoalKeeper keeper = (GoalKeeper) owner;
			if(keeper.team.pitch.getRegion(keeper.regionToUse).contains(keeper.pos(), 0.8)){
				keeper.targetPos.set(keeper.pos());
				keeper.FSM().changeState(KeeperTendGoal.state());
				return;
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.AP().arriveOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}
		
	}

	public static class KeeperTendGoal  extends State implements Constants {

		private static KeeperTendGoal instance = null;

		public static KeeperTendGoal state(){
			if(instance == null){
				instance = new KeeperTendGoal();
				instance.name = "Tend Goal";
			}
			return instance;
		}


		@Override
		public void enter(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.targetPos.set(keeper.getRearInterposeTarget());
			keeper.AP().arriveOn(keeper.targetPos, SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.targetPos.set(keeper.getRearInterposeTarget());
			keeper.AP().arriveOn(keeper.targetPos, SBF.FAST);

			if(keeper.isBallWithinRange()){//&& keeper.team.getReceivingPlayer() == null) {
				keeper.team.pitch.ball.trap(keeper);
				keeper.FSM().changeState(KeeperPutBallBackInPlay.state());
				return;
			}
			
			// If within intercept range and the other team are in control go for it
			if(keeper.isBallWithinInterceptRange() && !keeper.team.isInControl()){
				keeper.FSM().changeState(KeeperInterceptBall.state());
				return;
			}

			if(keeper.isBallWithinInterceptRange() && keeper.team.isInControl() && keeper.team.getReceivingPlayer() == null){
				keeper.FSM().changeState(KeeperInterceptBall.state());
				return;
			}

			if(keeper.tooFarFromGoalMouth() && keeper.team.isInControl()){
				keeper.FSM().changeState(PlayerReturnToRegion.state());
				return;
			}

		}

		@Override
		public void exit(BaseEntity owner) {
			GoalKeeper keeper = (GoalKeeper) owner;
			keeper.AP().arriveOff();	
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}
	}

	
	
	public static class PlayerGlobal extends State implements Constants {

		private static PlayerGlobal instance = null;

		public static PlayerGlobal state(){
			if(instance == null)
				instance = new PlayerGlobal();
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;	
			if(player.isControllingPlayer() && player.isBallWithinReceivingRange())
				player.maxSpeed(PlayerMaxSpeedWithBall);
			else
				player.maxSpeed(PlayerMaxSpeedWithoutBall);
		}

		@Override
		public void exit(BaseEntity owner) {}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			PlayerBase player = (PlayerBase) owner;
			Vector2D v = null;
			switch(tgram.msg){
			case RECEIVE_BALL:
				// Get position ball was kicked to
				v = (Vector2D) tgram.extraInfo[0];
				player.targetPos.set(v);
				player.FSM().changeState(PlayerReceiveBall.state());
				break;
			case SUPPORT_ATTACKER:
				if(player.FSM().isUsingState(PlayerSupportAttacker.state()))
					return true;
				player.FSM().changeState(PlayerSupportAttacker.state());
				return true;	
			case PLAYER_PREPARE_FOR_KICKOFF:
				player.FSM().changeState(PlayerPrepareForKickOff.state());
				return true;
			case GO_TO_REGION:
				player.FSM().changeState(PlayerReturnToRegion.state());
				return true;
			case WAIT:
				player.FSM().changeState(PlayerWait.state());
				return true;
			case PASS_TO_ME:
				// Only need to consider this if the field player is within kicking distance 
//				if(player.isFieldPlayer() && player.isBallWithinKickingRange()){
				if(player.team.getReceivingPlayer() == null && player.isBallWithinKickingRange()){
					// Get the player sending this message
					FieldPlayer receiver = (FieldPlayer)tgram.extraInfo[0];
					player.team.pitch.ball.kick(player, Vector2D.sub(receiver.pos(), player.team.pitch.ball.pos()), MaxPassingForce);
					Dispatcher.dispatch(0, player.ID(), receiver.ID(), RECEIVE_BALL, receiver.pos().get());
					player.FSM().changeState(PlayerWait.state());
					player.findSupport();
				}
				return true;
			}
			return false;
		}

	}

	
	public static class PlayerChaseBall extends State {


		private static PlayerChaseBall instance = null;

		public static PlayerChaseBall state(){
			if(instance == null){
				instance = new PlayerChaseBall();
				instance.name = "Chase ball";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().seekOn(player.team.pitch.ball.pos());
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			// If player in kick range change to kick ball state
			if(player.isBallWithinKickingRange()){
				player.FSM().changeState(PlayerKickBall.state());
				return;
			}
			// If this player is the closest to the ball continue chasing it
			if(player.isClosestTeamMemberToBall()){
				player.AP().seekOn(player.team.pitch.ball.pos());
				return;
			}
			// If player is not closest to ball he should return back to current region
			player.FSM().changeState(PlayerReturnToRegion.state());
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().seekOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	
	public static class PlayerDribble extends State implements Constants {


		private static PlayerDribble instance = null;

		public static PlayerDribble state(){
			if(instance == null){
				instance = new PlayerDribble();
				instance.name = "Dribble";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.team.setControllingPlayer(player);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			Vector2D direction;
			double dot = player.team.teamHeading.dot(player.heading());
			if(dot < 0){	// Facing wrong direction so turn round
				direction = new Vector2D(player.heading());
				double angle = -1 * FastMath.QUARTER_PI * direction.sign(player.team.teamHeading);
				direction = Transformations.vec2DRotateAroundOrigin(direction, angle);
				player.team.pitch.ball.kick(player, direction, MaxDribbleTurnForce);
			}
			else {
				// So not turning so dribble up the field towards the opponents goal Change this 
				// direction so it is more aligned with the opponents goal
				if(player.isInHotRegion()){
					Goal goal = player.team.getOtherGoal();
					double y = goal.centre.y;
					if(player.pos().y < GOAL_LOW_Y)
						y = GOAL_LOW_Y;
					else if(player.pos().y > GOAL_HIGH_Y)
						y = GOAL_HIGH_Y;
					direction = new Vector2D(goal.centre.x, y);	
					direction.sub(player.pos());
				}
				else {
					direction = new Vector2D(player.team.teamHeading);
				}
				player.team.pitch.ball.kick(player, direction, MaxDribbleForce);			
			}
			player.FSM().changeState(PlayerChaseBall.state());
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public static class PlayerKickBall extends State implements Constants {


		private static PlayerKickBall instance = null;

		public static PlayerKickBall state(){
			if(instance == null){
				instance = new PlayerKickBall();
				instance.name = "Kick ball";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			// Make everyone know this is the controlling player
			player.team.setControllingPlayer(player);
			// If we are not ready to kick then change to ChaseBall state
			if(!player.team.pitch.ball.readyToBeKickedBy(player))
				player.FSM().changeState(PlayerChaseBall.state());
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			Ball ball = player.team.pitch.ball;
			Vector2D ballPos = ball.pos();

			// Calculate the normalised vector to the ball
			Vector2D toBall = Vector2D.sub(ballPos, player.pos());
			toBall.normalize();
			// Calculate the dot product of the heading and toball vector
			double dot = player.heading().dot(toBall);

			// If there is a receiving player assigned or the ball is in the hands
			// of a goalkeeper or the player is facing away from the ball 
			// change to ChaseBall state.
			if(player.team.getReceivingPlayer() != null || player.team.pitch.ball.keeperHasBall() || dot < 0){
				player.FSM().changeState(PlayerChaseBall.state());
				return;
			}

			// See if we have a shot at goal
			double power = (0.6 + 0.4 * dot) * MaxShootingForce;
			Vector2D ballTarget = new Vector2D(); // populate this with the shot target
			// The probability of a shot increases as we get closer to opponents goal
			double prob = player.isInHotRegion() ? 0.8 : player.isInHotHalf() ? 0.6 : 0.05;
			if( Math.random() < prob && player.team.canShoot(ballPos, power, ballTarget)){
				player.team.addNoiseToKick(ballPos, ballTarget);
				Vector2D kickDirection = Vector2D.sub(ballTarget, ballPos);
				ball.kick(player, kickDirection, power);
				player.FSM().changeState(PlayerWait.state());
				player.findSupport();
				return;
			}
			
			// Attempt to pass the ball if we are threatened and a suitable receiver can be found
			power = (0.4 + 0.6 * dot) * MaxPassingForce;
			if (player.isThreatened()){
				PlayerBase receiver = player.team.findBestPass(player, ballTarget, power, FielderMinPassDistance);
				if(receiver != null){
					player.team.addNoiseToKick(ballPos, ballTarget);
					Vector2D kickDirection = Vector2D.sub(ballTarget, ballPos);
					ball.kick(player, kickDirection, power);
					Dispatcher.dispatch(0, player.ID(), receiver.ID(), RECEIVE_BALL, ballTarget);
					player.FSM().changeState(PlayerWait.state());
					player.findSupport();
					return;
				}
			}
			// Either we are not threatened or if threatened unable to find a suitable pass
			// so dribble
			player.findSupport();
			player.FSM().changeState(PlayerDribble.state());
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	
	public static class PlayerLeavesPitch  extends State implements Constants {

		private static PlayerLeavesPitch instance = null;

		public static PlayerLeavesPitch state(){
			if(instance == null){
				instance = new PlayerLeavesPitch();
				instance.name = "Leave Pitch";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.targetPos.set(player.offFieldPosition);
			player.heading(offPitchHeading);
			player.AP().arriveOn(player.targetPos, SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			if(player.isAtTarget()){
				Dispatcher.dispatch(0, player.ID(), player.team.ID(), AT_TARGET);
				player.moveTo(player.targetPos);
				player.FSM().changeState(PlayerWait.state());
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().arriveOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}
	
	}

	public static class PlayerPrepareForKickOff extends State implements Constants {

		private static PlayerPrepareForKickOff instance = null;

		public static PlayerPrepareForKickOff state(){
			if(instance == null){
				instance = new PlayerPrepareForKickOff();
				instance.name = "Prep for Kickoff";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.regionToUse = player.defendRegion;
			player.targetPos.set(player.team.pitch.getRegion(player.regionToUse).centre);
			player.targetPos.add(MathUtils.randomClamped(), MathUtils.randomClamped());
			player.headingAtRest(player.team.teamHeading);
			player.AP().arriveOn(player.targetPos, SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			if(player.isGoalKeeper()){ // Goal keeper
				if(player.isNearTarget()){
					Dispatcher.dispatch(0, player.ID(), player.team.ID(), AT_TARGET);
					player.FSM().changeState(KeeperTendGoal.state());			
				}
			}
			else { // Field player
				if(player.isAtTarget()){
					Dispatcher.dispatch(0, player.ID(), player.team.ID(), AT_TARGET);
					player.moveTo(player.targetPos);
					player.FSM().changeState(PlayerWait.state());
				}
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().arriveOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public static class PlayerReceiveBall extends State implements Constants {


		private static PlayerReceiveBall instance = null;

		public static PlayerReceiveBall state(){
			if(instance == null){
				instance = new PlayerReceiveBall();
				instance.name = "Receive ball";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.team.setReceivingPlayer(player);
			player.team.setControllingPlayer(player);

			// There are two types of receive behaviour. One uses arrive to direct
			// the receiver to the position sent by the passer in its telegram. The
			// other uses the pursuit behavior to pursue the ball. 
			// This statement selects between them dependent on the probability
			// ChanceOfUsingArriveTypeReceiveBehavior, whether or not an opposing
			// player is close to the receiving player, and whether or not the receiving
			// player is in the opponents 'hot region' (the third of the pitch closest
			// to the opponent's goal
			if(player.isInHotRegion() || 
					Math.random() < ChanceOfUsingArriveTypeReceiveBehavior &&
					!player.team.isOpponentWithinRadius(player.pos(), PassThreatDistance)){
				player.AP().arriveOn(player.targetPos, SBF.FAST);
			}
			else {
				player.AP().pursuitOn( player.team.pitch.ball);
			}
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;

			// If the player is close enough to the ball or his team loses control
			// then chase ball
			if(player.isBallWithinReceivingRange() || !player.team.isInControl()){
				player.FSM().changeState(PlayerChaseBall.state());
			}

			if(player.isAtTarget()){
				player.AP().arriveOff().pursuitOff();
				player.trackBall();
				player.velocity(0, 0);
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().arriveOff().pursuitOff();
			player.team.setReceivingPlayer(null);
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public static class PlayerReturnToRegion extends State implements Constants {


		private static PlayerReturnToRegion instance = null;

		public static PlayerReturnToRegion state(){
			if(instance == null){
				instance = new PlayerReturnToRegion();
				instance.name = "To Region";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.targetPos.set(player.team.pitch.getRegion(player.regionToUse).centre);
			player.targetPos.add(MathUtils.randomClamped(), MathUtils.randomClamped());
			player.AP().arriveOn(player.targetPos, SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			if(player.team.pitch.ballInPlay){
				// If this is the nearest player to the ball and no receiver has been allocated
				// and it is not in the possession of a goalkeeper then chase it
				if(player.isClosestTeamMemberToBall() && player.team.getReceivingPlayer() == null && !player.team.pitch.ball.keeperHasBall()){
					if(player.isGoalKeeper())
						player.FSM().changeState(KeeperInterceptBall.state());
					else
						player.FSM().changeState(PlayerChaseBall.state());
					return;
				}

				if(player.team.pitch.getRegion(player.regionToUse).contains(player.pos(), 0.5)){
					player.targetPos.set(player.pos());
					if(player.isGoalKeeper())
						player.FSM().changeState(KeeperTendGoal.state());
					else
						player.FSM().changeState(PlayerWait.state());
					return;
				}
			}
			// Game is not on
			if(!player.team.pitch.ballInPlay && player.isAtTarget()){
				if(player.isGoalKeeper())
					player.FSM().changeState(KeeperTendGoal.state());
				else
					player.FSM().changeState(PlayerWait.state());
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.AP().arriveOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	
	public static class PlayerSupportAttacker extends State implements Constants {


		private static PlayerSupportAttacker instance = null;
		
		public static PlayerSupportAttacker state(){
			if(instance == null){
				instance = new PlayerSupportAttacker();
				instance.name = "Support Attacker";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			// Find the best supporting position
			player.AP().arriveOn(player.team.getBestSupportngSpot(), SBF.FAST);
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			// If team is not in control then go to current region
			if(!player.team.isInControl() || player != player.team.getSupportingPlayer()){
				player.FSM().changeState(PlayerReturnToRegion.state());
				return;
			}
			// Update the best supporting position if required
			player.targetPos.set(player.team.getBestSupportngSpot());
			player.AP().arriveOn(player.targetPos, SBF.FAST);
			// See if this player has a possible shot at goal
			if(player.team.canShoot(player.pos(), MaxShootingForce, null)){
				player.team.requestPass(player);
			}
			if(player.isAtTarget()){
				player.AP().arriveOff();
				player.velocity(0, 0);
				player.trackBall();
				if(!player.isThreatened()){
					player.team.requestPass(player);			
				}
			}
			else if(!player.AP().isArriveOn()){
				player.AP().arriveOn();
			}
		}

		@Override
		public void exit(BaseEntity owner) {
			PlayerBase player = (PlayerBase) owner;
			player.team.setSupportingPlayer(null);
			player.AP().arriveOff();
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	
	public static class PlayerWait extends State implements Constants{


		private static PlayerWait instance = null;

		public static PlayerWait state(){
			if(instance == null){
				instance = new PlayerWait();
				instance.name = "Wait";
			}
			return instance;
		}

		@Override
		public void enter(BaseEntity owner) {
			//PlayerBase player = (PlayerBase) owner;
		}

		@Override
		public void execute(BaseEntity owner, double deltaTime, World world) {
			PlayerBase player = (PlayerBase) owner;
			if(!player.isNearTarget()){
				player.AP().arriveOn();
				return;
			}
			player.AP().arriveOff();
			player.velocity(0,0);
			if(player.team.pitch.ballInPlay)
				player.trackBall();

			if(player.team.pitch.ballInPlay){
				// If our team is in control and the player is ahead of the attacker then ask for a pass
				if(player.team.isInControl() && !player.isControllingPlayer() && player.isAheadOfAttacker()){
					player.team.requestPass(player);
					return;
				}
				// if closest player to ball and there is no assigned receiver and 
				// neither goalkeeper has the ball then chase it
				if(player == player.team.getPlayerClosestToBall() && 
						player.team.getReceivingPlayer() == null &&
						!player.team.pitch.ball.keeperHasBall()){
					player.FSM().changeState(PlayerChaseBall.state());
					return;
				}
			}
		}

		@Override
		public void exit(BaseEntity owner) {
		}

		@Override
		public boolean onMessage(BaseEntity owner, Telegram tgram) {
			return false;
		}

	}

	public interface Constants {

		// Player messages
		int RECEIVE_BALL 				= 101;
		int PASS_TO_ME					= 102;
		int SUPPORT_ATTACKER			= 103;
		int PLAYER_PREPARE_FOR_KICKOFF	= 104;
		int GO_TO_REGION				= 105;
		int TEND_GOAL					= 107;
		int AT_TARGET					= 108;
		int WAIT						= 199;

		// Team messages;
		int PREPARE_FOR_KICKOFF			= 201;
		int DEFENDING					= 202;
		int ATTACKING					= 203;
		
		// Pitch messages
		int TEAMS_PREPARE_FOR_KICK_OFF	= 301;
		int TEAMS_READY_FOR_KICK_OFF	= 302;
		int GOAL_SCORED					= 303;
		int STOP_GAME					= 304;
		
		// State IDs
		int PREPARE_FOR_KICKOFF_STATE	= 901;
		int DEFENDING_STATE				= 902;
		int ATTACKING_STATE				= 903;
		
		int GOALKEEPER					= 1001;
		int ATTACKER					= 1002;
		int DEFENDER					= 1003;
		
		float BALL_RADIUS				= 4.5f;
		double BALL_MASS				= 1.0;

		float PLAYER_RADIUS				= 5.5f;
		double PLAYER_MASS				= 1.0;


		float PITCH_LENGTH				= 600;
		float PITCH_WIDTH 				= 300;
		float GOAL_WIDTH				= 100;
		float GOAL_DEPTH				= 30;
		float GOAL_HIGH_Y				= (PITCH_WIDTH + GOAL_WIDTH)/2 - BALL_RADIUS;
		float GOAL_LOW_Y				= (PITCH_WIDTH - GOAL_WIDTH)/2 + BALL_RADIUS;
		float KEEPER_TEND_DIST			= GOAL_DEPTH/2;
		
		float MATCH_TIME				= 120;
		float TIMER_LENGTH				= 200;
		
		//these values tweak the various rules used to calculate the support spots
		double Spot_CanPassScore						= 2.0;
		double Spot_CanScoreFromPositionScore			= 1.0;
		double Spot_DistFromControllingPlayerScore		= 2.0;
//		double Spot_ClosenessToSupportingPlayerScore 	= 0.1;
//		double Spot_AheadOfAttackerScore				= 1.0;

		// The maximum time between recalculation of the support spots
		long SupportSpotUpdateInterval					= 1000;

		//the chance a player might take a random pot shot at the goal
		double ChancePlayerAttemptsPotShot				= 0.05;

		//this is the chance that a player will receive a pass using the arrive
		//steering behaviour, rather than Pursuit
		double ChanceOfUsingArriveTypeReceiveBehavior	= 0.5;

		
		double FRICTION_MAG								= -15.0;
		double FRICTION_MAG_SQ							= FRICTION_MAG * FRICTION_MAG;

		// The keeper has to be this close to the ball to be able to interact with it
		double KeeperInBallRange						= PLAYER_RADIUS + BALL_RADIUS; // was 10
		double KeeperInBallRangeSq						= KeeperInBallRange * KeeperInBallRange;
		double PlayerNearTargetRange					= 20.0;
		double PlayerNearTargetRangeSq					= PlayerNearTargetRange * PlayerNearTargetRange;
		double PlayerAtTargetRange						= 1.0;
		double PlayerAtTargetRangeSq					= PlayerAtTargetRange * PlayerAtTargetRange;

		// Player has to be this close to the ball to be able to kick it. The higher
		// the value this gets, the easier it gets to tackle. 
		double PlayerKickingDistance					= PLAYER_RADIUS + BALL_RADIUS + 2; // 12.0;
		double PlayerKickingDistanceSq					= PlayerKickingDistance * PlayerKickingDistance;

		// The minimum time (milliseconds) allowed between kicks by the same player
		long PlayerKickInterval							= 125;

		float PlayerMaxForce							= 1000;
		double PlayerMaxSpeedWithBall					= 36; // 30
		double PlayerMaxSpeedWithoutBall				= 66; // 60
		double KeeperMaxSpeedWithoutBall				= 45; // 60
		double PlayerMaxTurnRate						= 7.0;

		//when an opponents comes within this range the player will attempt to pass
		//the ball. Players tend to pass more often, the higher the value
		double PlayerComfortZone						= 80.0;
		double PlayerComfortZoneSq						= PlayerComfortZone * PlayerComfortZone;

		//in the range zero to 1.0. adjusts the amount of noise added to a kick,
		//the lower the value the worse the players get.
		double PlayerKickingAccuracy					= 0.95;

		//the number of times the SoccerTeam::CanShoot method attempts to find
		//a valid shot
		int NumAttemptsToFindValidStrike				= 5;

		// Forces that can be applied when kicking the ball
		double MaxShootingForce							= 220;
		double BaseForce								= 180;
		double MaxPassingForce							= BaseForce;
		double MaxDribbleForce							= BaseForce/4.6;
		double MaxDribbleTurnForce						= BaseForce/5;

		//the minimum distance a receiving player must be from the passing player
		double FielderMinPassDistance					= 120;
		//the minimum distance a player must be from the goalkeeper before it will
		//pass the ball
		double KeeperMinPassDistance					= 60.0;
		// Pass threat distance
		double PassThreatDistance						= 70.0;

		// when the ball becomes within this distance of the goalkeeper he
		// changes state to intercept the ball
		double GoalKeeperInterceptRange					= 80.0;
		double GoalKeeperInterceptRangeSq				= GoalKeeperInterceptRange * GoalKeeperInterceptRange;

		//how close the ball must be to a receiver before he starts chasing it
		double BallWithinReceivingRange					= 50.0;
		double BallWithinReceivingRangeSq				= BallWithinReceivingRange * BallWithinReceivingRange;

		Vector2D offPitchHeading = new Vector2D(0,-1);
	}
}
